﻿using System;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TaskGenerater
{
    public class Generater
    {
        public const string VoidString = "void";
        private readonly string _filePath;
        private Assembly _assembly;
        private Type[] _allTypes;
        private Type _clientType;
        private Type _channelType;

        public string Namespace { get; private set; }
        public string ClientName { get; private set; }
        public string MethodsString { get; private set; }

        public static string Generate(string directory)
        {
            var files = Helper.SerachFile(directory, "Reference.cs");
            if (files.Any() == false)
                throw new MyException("当前项目找不到服务引用!");

            var sb = new StringBuilder();
            foreach (var file in files)
            {
                sb.Append(GenerateOne(file));
                sb.AppendLine();
            }
            return Templete.MergeFile(sb.ToString());
        }

        public static string GenerateOne(string file)
        {
            var generater = new Generater(file);
            return generater.Generate();
        }

        private Generater(string filePath)
        {
            _filePath = filePath;
        }

        private string Generate()
        {
            _assembly = Helper.CompileToAssembly(_filePath);
            _allTypes = _assembly.GetTypes();
            _clientType = _allTypes.FirstOrDefault(t => t.Name.Contains("Client") && t.BaseType != null && t.BaseType.Name.Contains("ClientBase"));
            _channelType = _allTypes.FirstOrDefault(t => t.Name.Contains("ClientChannel"));
            if (_channelType == null)
                throw new MyException("找不到ClientChannel类!");

            if (_clientType == null)
                throw new MyException("找不到Client类!");

            Namespace = _clientType.Namespace;
            ClientName = _clientType.Name;
            MethodsString = GenerateMethods();
            return Templete.MergeClass(this);
        }

        private string GenerateMethods()
        {
            var sb = new StringBuilder();
            var channelMethods = _channelType.GetMethods();
            foreach (var beginMethod in channelMethods.Where(m => m.Name.StartsWith("Begin")))
            {
                var endMethodName = "End" + beginMethod.Name.Substring(5);
                var endMethod = channelMethods.FirstOrDefault(m => m.Name == endMethodName);
                if (endMethod == null)
                    throw new MyException(string.Format("找不到:{0}方法!", endMethodName));

                var oneMethodString = Templete.MergeMethod(GetMethodData(beginMethod, endMethod));
                sb.Append(oneMethodString);
                sb.AppendLine();
            }
            return sb.ToString();
        }

        private static dynamic GetMethodData(MethodInfo beginMethod, MethodInfo endMethod)
        {
            dynamic result = new ExpandoObject();
            var methodNameBase = beginMethod.Name.Substring(5);
            var parameters = beginMethod.GetParameters().Where(p => p.Name != "callback" && p.Name != "asyncState").ToArray();

            result.ReturnType = VoidString;
            result.ParamsNames = string.Empty;
            result.ParamsTypeAndNames = string.Empty;
            result.MethodName = methodNameBase + "TaskAsync";
            result.MethodShortName = methodNameBase;
            result.BeginMethod = beginMethod.Name;
            result.EndMethod = endMethod.Name;

            if (endMethod.ReturnType.Name.Equals(VoidString, StringComparison.OrdinalIgnoreCase) == false)
            {
                result.ReturnType = endMethod.ReturnType.IsGenericType ? GetGenericTypeString(endMethod.ReturnType) : endMethod.ReturnType.Name;
            }
            if (parameters.Any())
            {
                result.ParamsTypeAndNames = string.Join(",", parameters.Select(p =>
                {
                    var typeName = p.ParameterType.IsGenericType ? GetGenericTypeString(p.ParameterType) : p.ParameterType.Name;
                    return string.Format("{0} {1}", typeName, p.Name);
                }));
                result.ParamsNames = string.Join(",", parameters.Select(p => p.Name));
            }
            return result;
        }

        private static string GetGenericTypeString(Type returnType)
        {
            if (returnType.IsGenericType == false)
                return string.Empty;

            var genericTypeName = returnType.GetGenericTypeDefinition().Name;
            genericTypeName = genericTypeName.Substring(0, genericTypeName.LastIndexOf('`'));
            var genericArguments = returnType.GetGenericArguments().Select(a => a.Name);
            return string.Format("{0}<{1}>", genericTypeName, string.Join(",", genericArguments));
        }
    }
}